var Protomean = Class.create();

Protomean.prototype.initialize = function(el, opts) {
	
	this._el = $(el);
	this._opts = Object.extend(Protomean.defaults, opts);

	if (Prototype.Browser.Gecko) {
		Object.extend(this, new Protomean_Gecko());
	} else if (Prototype.Browser.IE) {
		Object.extend(this, new Protomean_Explorer());
	} else {
		this._opts.onunsupported();
		return;
	}


	this._index = Protomean.index++;
	Protomean.instances[this._index] = this;


	var tools = this._opts.tools;
	
	
	var tpl = new Template(this._opts.tplToolItem);
	
	var toolsHtml = "";
	var imgPath = this._opts.imgPath;
	
	tools.each(function(tool) {
		toolsHtml += tpl.evaluate(Object.extend(tool, {imgPath : imgPath}));
	});
	
	tpl = new Template(this._opts.tplTools);
	
	toolsHtml = tpl.evaluate({ toolsItems: toolsHtml });

	var containers = $A(this._opts.containers);
	tpl = new Template(this._opts.tplContainerItem);
	
	var containersHtml = "";
	containers.each(function(cont) {
		containersHtml += tpl.evaluate(cont);
	});
	
	tpl = new Template(this._opts.tplContainers);
	
	containersHtml = tpl.evaluate({ containers: containersHtml });

	tpl = new Template(this._opts.tplIframe);

	var iframeHtml = tpl.evaluate({ index: this._index, protomeanBasePath: Protomean.getBasePath() });


	tpl = new Template(this._opts.tplBox);
	var boxHtml = tpl.evaluate({tools: toolsHtml, iframe: iframeHtml, containers: containersHtml, toc: this._opts.tplToc });

	var box = document.createElement('div');
	box.innerHTML = boxHtml;
	this._box = box.firstChild;
	Element.hide(this._el);
	this._el.parentNode.insertBefore(this._box, this._el);
	this._view = 'editor';
};

Protomean.prototype.bindEvents = function() {
	
	var ed = this;
	
	var b = $(this._box);
	b.getElementsBySelector('button.protomean-tool-item')
			.each(function(el) {
				Event.observe(el, "click", ed.toolbarItemClick.bindAsEventListener(ed));
			});
	b.getElementsBySelector('select.pm-containers')
		.each(function(el) {
			Event.observe(el, "change", ed.containerItemChange.bindAsEventListener(ed));
		});

	b.getElementsBySelector('select.protomean-toc')
		.each(function(el) {
			Event.observe(el, "change", ed.tocChange.bindAsEventListener(ed));
		});


	if (this._el.form) {
		Event.observe(this._el.form, 'submit', this.synchronize.bindAsEventListener(this));
	}
	
	
	Event.observe(window, 'scroll', this.setToolbarPosition.bind(this));
	Event.observe(window, 'resize', this.setToolbarPosition.bind(this));
	
	this.setToolbarPosition();
	this.resize();
	
	// set on a timeout because IE doesn't want to collaborate
	setTimeout(function() {
		ed.updateToc();
		ed.setSaved();
	}, 0);

};

Protomean.prototype.toolbarItemClick = function(e) {
	this.exec(Event.element(e).name);
	try {
	this._iframe.contentWindow.focus();
} catch(e){}
};

Protomean.prototype.containerItemChange = function(e) {
	var el = Event.element(e);
	var v = el[el.selectedIndex].value;
	this.switchTo(this.getFirstBlockParent(this.selected()), v);
	this.updateToc();
	this._iframe.contentWindow.focus();
};

Protomean.prototype.updateToc = function() {
	var select = $(this._box).getElementsBySelector('select.protomean-toc')[0];
	if (!select) return;
	select.innerHTML = '';
	var col = this._doc.getElementsByTagName('*');
	var option, level;
	for (var i = 0; i < col.length; ++i) {
		if (col[i].tagName.toLowerCase().search(/^h[1-6]$/) != -1) {
			level = (/([1-6])/).exec(col[i].tagName)[1];
			option = this.updateToc_getOption(col[i], level);
		select.options[select.options.length] = option;
		}
	}
	if (select.options.length == 0) {
		select.options[0] = new Option('--', '');
	}
};

Protomean.prototype.updateToc_getOption = function(el, level) {
	var content = el.innerHTML.stripTags().strip();
	var id = el.id || Protomean.assignId(el);
	var optText = '';
	(+level - 1).times(function() {
		optText += '-';
	});
	optText += ' ' + content;
	var option;
	if (Prototype.Browser.IE) {
		var span = document.createElement('span');
		span.innerHTML = optText; 
		option = new Option(span.innerText, id);
	} else {
		option = document.createElement('option');
		option.value = id;
		option.innerHTML = optText;
	}
	return option;
};

Protomean.prototype.tocChange = function(e) {
	var value = Event.element(e).value;
	var dest = this._doc.getElementById(value);
	var offset = Position.cumulativeOffset($(this._box))[1] - $(this._box).getElementsBySelector('button.protomean-tool-item')[0].offsetHeight - 20;

	if (Effect && Effect.ScrollTo)
		new Effect.ScrollTo(dest, {duration: this._opts.scrollDuration, offset: offset, afterFinish: function() {
			// TODO
			// does not work into the iframe
			// new Effect.Highlight(dest);
		} });

	else
		this._doc.location.hash = value;

	// returns focus to editor
	try {
		this._iframe.contentWindow.focus();
	} catch(e){}
	
};

Protomean.prototype.updateTocLocation = function() {
	var sel = this.selected();
	if (!sel.tagName) return;
	var tag = sel.tagName.toLowerCase();
	var header;
	header = getSectionHeader(sel);
	if (header) {
		var id = header.id;
		var select = $(this._box).getElementsBySelector('select.protomean-toc')[0];
		for (var i = 0; i < select.options.length; ++i) {
			if (id == select.options[i].value) {
				select.selectedIndex = i;
				return;
			}
		}
	}
	
	
	function getSectionHeader(el) {
		if (!el)
			return null;
		if (!el.tagName) {
			return getSectionHeader(el.previousSibling);
		}
		var tag = el.tagName.toLowerCase();
		var header;
		if (tag.search(/^h[1-6]$/) != -1) {
			header = el;
		} else {
			header = getSectionHeader(el.previousSibling);
		}
		return header;
	}
};

Protomean.assignId = function(el) {
	if (el.id)
		return el.id;
	return el.id = 'toc-id-' + Protomean.assignId.c++;
};

Protomean.assignId.c = 0;


Protomean.prototype.hasChanged = function() {
	return !(this.savedHTML == this._doc.body.innerHTML);
};

Protomean.prototype.setSaved = function() {
	this.savedHTML = this._doc.body.innerHTML;
};

Protomean.prototype.exec = function(op) {
	if (typeof this._actions[op] == 'function')
		this._actions[op].call(this);
	else
		this._exec(op);
};

Protomean.prototype.switchTo = function(node,sType) {
  var newNode = this._doc.createElement(sType);
  var html = node.innerHTML;
  node.parentNode.replaceChild(newNode,node);
  newNode.innerHTML = html;
  this.setFocusToNode(newNode);
};

Protomean.prototype._exec = function(op, param) {
	switch (op) {
		
		case 'indent':
		case 'outdent':
			var focusNode = this.selected();
			var anchorNode = this.selected('anchor');

			focusNode = this.getFirstBlockParent(focusNode);
			anchorNode = this.getFirstBlockParent(anchorNode);
			
			if(focusNode && focusNode == anchorNode
        && focusNode.tagName.toLowerCase() == 'li') {
      	
          var ancestor = focusNode.parentNode.parentNode;
      	
          if(focusNode.parentNode.childNodes.length>1
      		|| ancestor.tagName.toLowerCase() == 'ol'
      		|| ancestor.tagName.toLowerCase() == 'ul')
      		
              this._doc.execCommand(op, false, null);
      }
			break;
		default:
			this._doc.execCommand(op, false, param);
	}

	//set to P if parent = BODY
	// only needed for Gecko Browsers
	// warning: may throw 'out of memory' errors in IE when manipulating various elements
	if (Prototype.Browser.Gecko) {
	  var container = this.selected();
	  if(container.tagName.toLowerCase() == 'body') {
			try	{
	      this._exec('formatBlock', 'p');
			} catch(e){}
		}
	}

	this.handleNodeChange();
};

Protomean.prototype.getHtml = function() {
	if (!this._cleaner)
		this._cleaner = new Protomean_Cleanup();
	return this._cleaner.cleanup(this._doc.body.innerHTML);
};

Protomean.prototype.getValue = function() {
	
	function replace(node, nname) {
		var n = document.createElement(nname);
		node.parentNode.replaceChild(n, node);
		n.innerHTML = node.innerHTML;
	}
	
	// replace strong with b, em with i
	var b = document.createElement('body');
	b.innerHTML = this._el.value;
	var els = b.getElementsByTagName('*');
	for (var i = 0; i < els.length; i++) {
		switch (els[i].nodeName.toLowerCase()) {
			case 'em':
				replace(els[i], 'i');
				break;
			case 'strong':
				replace(els[i], 'b');
				break;
		}
	}
	return b.innerHTML;
};

Protomean.prototype.synchronize = function() {
	// disable toolbar buttons, IE is a moron
	$(this._box).getElementsBySelector('button.protomean-tool-item').each(function(item) {
		item.setAttribute('disabled', 'disabled');
	});
	
	this._el.value = this.getHtml();
};

Protomean.prototype._actions = {
	
	toggleHtml : function() {
		if (this._view == 'editor') {
			this._view = 'textarea';
			this._el.value = this.getHtml();
			$(this._iframe).hide();
			$(this._el).show();
		} else {
			this._view = 'editor';
			this._doc.body.innerHTML = this.getValue();
			this._iframe.show();
			this._el.hide();
		}
	}
	,
	link : function() {
		// FIXME there has to be a better way
		var el;
		if (window.getSelection) { // Mozilla
			this._doc.execCommand('fontname', false, 'protomean-temp');
			el = this._doc.getElementsByTagName('font')[0];
			if (el) {
				el = el.firstChild;	
				this._doc.execCommand('undo', false, null);
			}
		} else { // Explorer
			el = this.selected();
		}
		if (!el) {
			return;
		}
		var href = '';
		var isLink = false;
		if (el && el.tagName) {
	 		if (el.tagName.toLowerCase() == 'a') {
				href = el.getAttribute('href');
				isLink = true;
			}
		}
		var r = prompt(this._opts.msgLinkPrompt, href);
		if (r) {
			this._exec('createlink', r);
		}
		else {
			if (isLink)
				this._exec('unlink');
		}


	}
	
	
};

Protomean.prototype.handleNodeChange = function() {

	var node = this.selected();

	if (node == this._lastSelected)
		return;
	this._lastSelected = node;
	
	// clean buttons status
	this._box.getElementsBySelector('button').each(function(it) {
		$(it).removeClassName('protomean-selected');
		if (it.name == 'indent' || it.name == 'outdent')
			it.setAttribute('disabled', 'disabled');
	});


	
	
	function selectButton(n, ed) {
		switch (n.tagName.toLowerCase()) {
			case 'b':
			case 'strong':
				ed.selectButton('bold');
				break;
			case 'i':
			case 'em':
				ed.selectButton('italic');
				break;
			default:
				ed.selectButton(n);
		}
	}
	
	// set buttons status
	var p = node;
	while (p && p.tagName.toLowerCase() != 'body') {
		selectButton(p, this);
		p = p.parentNode;
	}
	
	
	// set list buttons status
	if (node.tagName.toLowerCase() == 'li') {
		var name = node.parentNode.tagName.toLowerCase() == 'ul' ? 'insertUnorderedList' : 'insertOrderedList';
		this.selectButton(name, this);
		this._box.getElementsBySelector('[name=indent]', '[name=outdent]')
			.each(function(el) {
				el.removeAttribute('disabled');
			});
	}


	// update containers select
	var block = this.getFirstBlockParent(node);
	var select = this._box.getElementsBySelector('select.pm-containers')[0];
	var found = false;
	if (block) {
		for (var i = 0, opt; (opt = select.options[i]); ++i) {
			if (opt.value == block.tagName.toLowerCase()) {
				opt.selected = 'selected';
				found = true;
				break;
			}
		}
	}
	if (!found)
		select.options[0].selected = 'selected';
	
	// update TOC location
	this.updateTocLocation();
};

Protomean.prototype.selectButton = function(n) {
	var b = $(this._box).getElementsBySelector('[name='+ n +']')[0];
	if (b) $(b).addClassName('protomean-selected');
};


Protomean.prototype.getFirstBlockParent = function(el) {
	if (Protomean.isBlockElement(el))
		return el;
	return el.parentNode ? this.getFirstBlockParent(el.parentNode) : null;
};

Protomean.prototype.skin = function() {
	var link = this._doc.createElement('link');
	link.setAttribute('rel', 'stylesheet');
	link.setAttribute('href', this._opts.skinCSS);
	this._doc.getElementsByTagName('head')[0].appendChild(link);
};

Protomean.prototype.resize = function() {
	if (!this._resizer)
		this._resizer = new PeriodicalExecuter(this.resize.bind(this), 0.1);
	try { // FIXME: God knows there's a better solution
		if (!this._minheight)
			this._minheight = this._iframe.offsetHeight;
		var bodyh = this._doc.body.scrollHeight;
		var iframeh = this._iframe.offsetHeight;
		var nh = bodyh + 40;
		if (nh != iframeh && nh >= this._minheight)
			if (Prototype.Browser.Gecko && nh > 32000) {
				this._iframe.style.height = '32000px';
				this._iframe.style.overflow = 'auto';
			} else {
				this._iframe.style.height = nh + 'px';
				this._iframe.style.overflow = 'hidden';
			}
	} catch(e) {
	}
};

Protomean.prototype.setToolbarPosition = function() {

  var toolbar = $('protomean-toolbar');
  var iframe = $(this._iframe);
  var min_top = Position.positionedOffset(iframe)[1];
  if (toolbar.getStyle('position') == 'static')
    min_top = min_top - toolbar.getHeight();
  var max_top = iframe.getHeight() - toolbar.getHeight();
  var top = Position.realOffset(toolbar)[1];
  max_top = min_top + max_top;
  
  toolbar.setStyle({ width: iframe.getWidth()+'px' });
  
  if (max_top < top) {
    toolbar.setStyle({ display: 'none' });
  } else {
    toolbar.setStyle({ display: '' });
  }
  
  if (Prototype.Browser.IE) {
    if (min_top > top) {
      top = min_top;
    }
    
    toolbar.setStyle({ position: 'absolute', top: top+'px'});
  } else {
    var position = 'absolute';
    
    if (min_top > top) {
      top = min_top;
    } else {
      top = 0;
      position = 'fixed';
    }
    
    toolbar.setStyle({ position: position, top: top+'px'});
  }
};

Protomean.defaults = {
	
	tplBox : "<div class='pm-box'>"
              + "<div class='pm-toolbar' id='protomean-toolbar'><div class='protomean-toolbar-inner'>"
							+ "<ul>"
              + "#{containers} #{tools} #{toc}"
							+ "</ul>"
              + "</div></div>"
              + "<div class='pm-main-area'>"
              + "#{html}"
              + "#{iframe}"
              + "#{status}"
              + "</div>"
              + "</div>"
,
	tplTools :  "#{toolsItems}"
,

	tplToolItem : "<li><button type='button' name='#{name}' class='protomean-tool-item #{cssClass}'><img src='#{imgPath}/#{cssClass}.png' alt='#{title}' /></button></li>"
,
	tplIframe : "<div class='wym_iframe wym_section'>"
              + "<iframe "
							+ "frameborder='0' "
							+ "scrolling='no' "
              + "src='"
              + "#{protomeanBasePath}/"
              + "iframe/iframe.html' "
              + "onload='window.parent.Protomean.instances["
              + "#{index}].initIframe(this)' "
              + "></iframe>"
              + "</div>"
,
	tools : [
		{ name : "bold", title: "bold", cssClass: "protomean-tool-bold" },
		{ name : "italic", title: "italic", cssClass: "protomean-tool-italic" },
		{ name : "insertUnorderedList", title: "·", cssClass: "protomean-tool-insertUnorderedList" },
		{ name : "insertOrderedList", title: "1", cssClass: "protomean-tool-insertOrderedList" },
		{ name : "outdent", title: "<-", cssClass: "protomean-tool-outdent" },
		{ name : "indent", title: "->", cssClass: "protomean-tool-indent" },
		// {'name': 'Undo', 'title': 'Undo', 'cssClass': 'protomean-tool-undo'},
    // {'name': 'Redo', 'title': 'Redo', 'cssClass': 'protomean-tool-redo'},
    {'name': 'link', 'title': 'Link', 'cssClass': 'protomean-tool-link'}
    // {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
    // {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
    // {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
    // {'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'},
    // {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'},
    // {'name': 'toggleHtml', 'title': 'HTML', 'cssClass': 'protomean-tools-html'}

    
	]
,
	tplContainers : "<li><select class='pm-containers'>#{containers}</select></li>"
,
	tplContainerItem : "<option value='#{name}'>#{title}</option>"
,
	containers : [
		{ name: 'p', title: 'párrafo' },
		{ name : "h1", title : "cabecera nivel 1" },
		{ name : "h2", title : "cabecera nivel 2" },
		{ name : "h3", title : "cabecera nivel 3" },
		{ name : "h4", title : "cabecera nivel 4" },
		{ name : "h5", title : "cabecera nivel 5" },
		{ name : "h6", title : "cabecera nivel 6" }
	]
,
	tplToc : "<li><select class='protomean-toc'><option>--</option></select>"
,
	imgPath : '/images/icons'
,
	skinCSS : '/css/editor.css'
,
	onunsupported : function() {
		
	}
,
	msgLinkPrompt : 'Introduzca la URL de destino'
,
	scrollDuration: 0.5
};

Protomean.instances = [];

Protomean.index = 0;

Protomean.getBasePath = function() {
	if (Protomean.basePath)
		return Protomean.basePath;
	var scripts = document.getElementsByTagName('script');
	var pos;
	for (var i = 0, s; (s = scripts[i]); i++) {
		if (s.src && (pos = s.src.search(/(^|\/)protomean\.js$/)) !== -1)
			return Protomean.basePath = s.src.substr(0, pos) || '.';
	}
};

Protomean.blockElements = [
	"address", "blockquote", "div", "dl", "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt", "li", "tbody", "td", "tfoot", "th", "thead", "tr"
];

Protomean.isBlockElement = function(el) {
	if (el.nodeType != 1)
		return false;
	var n = el.tagName.toLowerCase();
	return $A(Protomean.blockElements).indexOf(n) != -1;
};




var Protomean_Cleanup = Class.create();

Protomean_Cleanup.prototype = {
	
	initialize: function(c) {
		this._config = c;
	}
,
	cleanup : function(html) {
		var o = html;
		o = this.removeEmptyElements(o);
		o = this.fixNestedLists(o);
		var b = document.createElement('body');
		b.innerHTML = o;
		o = this.cleanHTML(b);
		o = o.innerHTML;
		o = this.lowercaseTags(o);
		return o;
	}
,
	fixNestedLists : function(html) {
		var cleanup = this;
		var b = document.createElement('body');
		b.innerHTML = html;
		for (var i = 0, it; (it = b.childNodes[i]); ++i) {
			if (it.nodeType == 1)
				$(it).cleanWhitespace();
			if (it.tagName && it.tagName.toLowerCase() == 'li') {
				var next = it.nextSibling;
				if (next && next.tagName && (next.tagName.toLowerCase() == 'ul' || next.tagName.toLowerCase() == 'ol')) {
					it.insertBefore(next, it.lastChild.nextSibling);
					--i;
				}
			}
			if (it.nodeType == 1 && it.innerHTML)
				it.innerHTML = this.fixNestedLists(it.innerHTML);
			
		}
		return b.innerHTML;
	}
,
	removeEmptyElements : function(html) {
		// FIXME
		// una vez eliminado un elemento, debería chequear si su padre sigue teniendo contenido
		var b = document.createElement('body');
		b.innerHTML = html;
		var nodes = b.getElementsByTagName('*');
		var node;
		for (var i = 0; i < nodes.length; i++) {
			node = nodes[i];
			if (node.childNodes.length == 0 || node.innerHTML == '&nbsp;') {
				node.parentNode.removeChild(node);
				i--;
			}
		}
		return b.innerHTML;
	}
,
	lowercaseTags : function(html) {
		html = html.replace(/(<)([a-z0-9-]+)(.*?\/?>)/ig, function(m, open, tag, close) {
			return open + tag.toLowerCase() + close;
		});
		
		html = html.replace(/(<\/)([a-z0-9-]+)(>)/gi, function(m, open, tag, close) {
			return open + tag.toLowerCase() + close;
		});
		
		return html;
	}
, 
	cleanHTML : function(node) {
		
		var rules = {
			'b' : [ 'strong' ],
			'i' : [ 'em' ],
			'a' : [ null, ['class', 'href', 'hreflang', 'title']],
			'p' : [],
			'ol' : [],
			'ul' : [],
			'li' : [],
			'h1' : [null, ['id']],
			'h2' : [null, ['id']],
			'h3' : [null, ['id']],
			'h4' : [null, ['id']],
			'h5' : [null, ['id']],
			'h6' : [null, ['id']],
			'strong' : [],
			'em' : [],
			'span' : [],
			'var' : [ null, ['value', 'name', 'type']]
		};
		
		var out = '';
		
		if (node.nodeType != 1)
			return node.nodeValue;
		
		var hc = node.hasChildNodes();
		var nn = node.tagName.toLowerCase();
		var r, newname, h;
		for (var x in rules) {
			newname = null;
			r = rules[x];
			if (x == nn) {
				newname = r[0] ? r[0] : nn;
				break;
			}
		}
		
		h = '';
		
		if (newname) {
			h += '<' + newname;
			
			var attrs = rules[x][1] || [];
			var val;
			
			for (var j = 0; j < attrs.length; j++) {
				val = node.getAttribute(attrs[j]);
				if (val)
					h += ' ' + attrs[j] + '="'+ val + '"'; // FIXME Escape quotes
	
			}
			
			
			h += '>';
		}
		
		if (hc) {
			for (var i = 0; i < node.childNodes.length; i++) {
				var res = this.cleanHTML(node.childNodes[i]);
				h += res.innerHTML ? res.innerHTML : res; 
			}
		}

		if (newname)
			h += '</' + newname + '>';				


		var b = document.createElement('body');
		b.innerHTML = h;
		return b;
			
	}

};